Skip to content

Fix Android issue with isUserInteraction always returning true when location puck is pulsing - #4222

Open
KijongHan wants to merge 7 commits into
rnmapbox:mainfrom
KijongHan:fix/issue-with-isUserInteraction-always-returning-true-when-location-puck-is-pulsing
Open

Fix Android issue with isUserInteraction always returning true when location puck is pulsing#4222
KijongHan wants to merge 7 commits into
rnmapbox:mainfrom
KijongHan:fix/issue-with-isUserInteraction-always-returning-true-when-location-puck-is-pulsing

Conversation

@KijongHan

Copy link
Copy Markdown
Contributor

Description

Fixes #3805

Fixes Android bug where onRegionIsChanging's properties.isUserInteraction was stuck true whenever <LocationPuck pulsing="default" /> was mounted, even if there were no user action. Root cause: the LocationComponent's pulsing animator keeps the map perpetually non-idle (as reported here), so Mapbox's MapIdle event never fires.

This means that CameraChangeTracker never cleared user gesture state as addOnMapIdleListener never fired if that <LocationPuck /> component was mounted. Fix was to use the new custom MapSteady event already introduced here #4074

Changes

  • Update CameraChangeTracker and replace single reason: CameraChangeReason field with reasonByGesture: Map<MapGestureType, CameraChangeReason> so concurrent move/scale/rotate gestures don't cause race conditions.
  • Decouple MapSteadyDetector from RNMBXCameraGestureObserver and use this custom event from RNMBXMapView to drive clearing the CameraChangeTracker state which corrects the downstream propagation to properties.isUserInteraction
  • Minor refactor so MapGestureType is promoted to shared enum

Checklist

  • I've read CONTRIBUTING.md
  • I updated the doc/other generated code with running yarn generate in the root folder
  • I have tested the new feature on /example app.
    • In V11 mode/ios (no, but shouldn't be affected)
    • In New Architecture mode/ios (no, but shouldn't be affected)
    • In V11 mode/android
    • In New Architecture mode/android
  • I added/updated a sample - if a new feature was implemented (/example)

Component to reproduce the issue you're fixing

Just pasting the same repro code snippet provided by the original issue

import { useEffect, useRef } from 'react';
import {
    MapView,
    ShapeSource,
    LineLayer,
    Camera,
    LocationPuck,
} from '@rnmapbox/maps';
import type { CameraRef } from '../../../src/components/Camera';

const aLine = {
    type: 'LineString',
    coordinates: [
        [-74.00597, 40.71427],
        [-74.00697, 40.71527],
    ],
}

const BugReportExample = () => {

    const mapCameraRef = useRef<CameraRef>(null)
    const locationPuck = true // Set this to false to hide the location puck. The issue only presents itself when the locationPuck is shown (and more specifically when this is set pulsing={"default"})

    useEffect(() => {
        // Re-center the map every 5 seconds. When this is done, isUserInteraction should log as false (but it doesn't when locationPuck is true)
        const interval = setInterval(() => {
            mapCameraRef.current?.setCamera({
                centerCoordinate: [-74.00597, 40.71427],
                animationDuration: 1000,
                animationMode: 'linearTo',
            })
        }, 5_000)

        return () => {
            clearInterval(interval)
        }

    }, [])

    const regionIsChanging = (region: any) => {
        console.log("isUserInteraction:", region.properties.isUserInteraction)
    }

    return (
        <MapView style={{ flex: 1 }} onRegionIsChanging={regionIsChanging}>
            <Camera ref={mapCameraRef} centerCoordinate={[-74.00597, 40.71427]} zoomLevel={14} />
            <ShapeSource id="idStreetLayer" shape={aLine as any}>
                <LineLayer id="idStreetLayer" />
            </ShapeSource>
            {locationPuck ? <LocationPuck puckBearingEnabled={true} puckBearing="heading" pulsing={"default"} /> : null}
        </MapView>
    )
}

export default BugReportExample;

@KijongHan
KijongHan temporarily deployed to CI with Mapbox Tokens June 1, 2026 22:36 — with GitHub Actions Inactive
@KijongHan
KijongHan had a problem deploying to CI with Mapbox Tokens June 1, 2026 22:36 — with GitHub Actions Failure
@KijongHan
KijongHan temporarily deployed to CI with Mapbox Tokens June 1, 2026 22:36 — with GitHub Actions Inactive
@KijongHan KijongHan changed the title Fix issue with isUserInteraction always returning true when location puck is pulsing Fix Android issue with isUserInteraction always returning true when location puck is pulsing Jun 1, 2026
@mfazekas

Copy link
Copy Markdown
Contributor

@KijongHan thanks much for the PR.

I'd rather add onRegionIsChanging or similar even to CameraGestureObserver. And kinda deprecate onRegionIsChanging

… gestures being tracked that could lead to race conditions
…t map steady event detection is decoupled and can be minimally reused from RNMBXMapView
…ent kotlin components

- refactor MapSteadyDetector so that it uses enum instead of magic strings
@KijongHan

Copy link
Copy Markdown
Contributor Author

@KijongHan thanks much for the PR.

I'd rather add onRegionIsChanging or similar even to CameraGestureObserver. And kinda deprecate onRegionIsChanging

thanks for reviewing these changes @mfazekas !

Just wanted to get some clarification on your suggestion. Do you mean deprecate onRegionIsChanging or onMapSteady?

If you mean the former, do you mean that we want to keep the underlying onRegionIsChanging event listener in CameraGestureObserver for internal logic but deprecate it from the javascript/React code?

@mfazekas

Copy link
Copy Markdown
Contributor

@KijongHan thanks much for the PR.
I'd rather add onRegionIsChanging or similar even to CameraGestureObserver. And kinda deprecate onRegionIsChanging

thanks for reviewing these changes @mfazekas !

Just wanted to get some clarification on your suggestion. Do you mean deprecate onRegionIsChanging or onMapSteady?

If you mean the former, do you mean that we want to keep the underlying onRegionIsChanging event listener in CameraGestureObserver for internal logic but deprecate it from the javascript/React code?

To clarify: deprecate onRegionIsChanging (the MapView event), not onMapSteady.

RNMBXMapView is already too complicated and the long-term plan is to split it into components — so I'd rather move code out of MapView, not into it. Concretely: CameraGestureObserver gains an onRegionIsChanging-like event (say onCameraChange) reporting isUserInteraction from its own gesture/animator tracking. Since it doesn't depend on MapIdle, it's immune to the pulsing puck issue. The MapView event stays as-is natively and gets deprecated in the JS API, so #3805 is addressed by migrating to the new event. Extracting MapSteadyDetector as a helper is fine — it should just stay owned by CameraGestureObserver, and the CameraChangeTracker changes wouldn't be needed.

KijongHan and others added 4 commits July 19, 2026 10:19
The MapSteadyDetector instance in RNMBXMapView only reset the camera
change reason to NONE and is no longer needed. Remove it and its import.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a new onMapCameraChange event that emits a camera snapshot
(center, bounds, zoom, heading, pitch, isUserInteraction, timestamp)
on every camera change.

- add MapCameraChangeDetector to derive the change reason from live
  gesture/animation state and forward CameraChanged events
- add MapCameraChangeEvent to serialize the snapshot into the JS shape
- wire hasOnMapCameraChange prop and onMapCameraChange event through the
  observer, view manager, JS wrapper and codegen spec

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- add onMapCameraChange example under CameraGestureObserver
@KijongHan
KijongHan force-pushed the fix/issue-with-isUserInteraction-always-returning-true-when-location-puck-is-pulsing branch from 928330f to 9699111 Compare July 31, 2026 01:52
@KijongHan
KijongHan requested a deployment to CI with Mapbox Tokens July 31, 2026 01:52 — with GitHub Actions Waiting
@KijongHan
KijongHan requested a deployment to CI with Mapbox Tokens July 31, 2026 01:52 — with GitHub Actions Waiting
@KijongHan
KijongHan requested a deployment to CI with Mapbox Tokens July 31, 2026 01:52 — with GitHub Actions Waiting
@KijongHan

Copy link
Copy Markdown
Contributor Author

I see, @mfazekas. I think I understand your reasoning here. I created a parallel MapCameraChangeDetector.kt and accompanying MapCameraChangeEvent event. The new onMapCameraChange event handler subscribes to the underlying mapboxMap.subscribeCameraChanged native event.

I extended the example for CameraGestureObserver and added the above handler and an auto-center effect to default coordinates and outputs the isUserInteraction event property. I can see that the reported issue is resolved using this new event handler.

One thing to note Im sure you're aware is that the iOS implementation is not done here. And because we are extending on CameraGestureObserver (instead of extending on the existing MapView event handler) we need to introduce it to iOS too. I'm keen to get your eyes on these sets of changes to see if Im on the right track here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: isUserInteraction is always true when locationPuck is shown on Android

2 participants